#include <errno.h>
#include <sstream>
#include <string>
#include <algorithm>

#include "SocketHelper.hpp"

//#pragma warning(disable : 4267) 
#define NI_MAXHOST 1025

namespace cybertron
{
	union_address::union_address(bool isV4)
		:_v4(isV4)
		, _scopeid(0)
	{
		if (_v4) {
			_addr.v4.s_addr = 0;
		}
		else {
			memset(&_addr.v6, 0, sizeof(_addr.v6));
		}
	}

	union_address::union_address(uint32_t ip)
		:_v4(true)
		, _scopeid(0)
	{
		_addr.v4.s_addr = ip;
	}

	union_address::union_address(uint8_t ip[16], uint32_t scope)
		:_v4(false)
		, _scopeid(scope)
	{
		memcpy(&_addr.v6, ip, sizeof(_addr.v6));
	}

	union_address::union_address(const union_address & a)
	{
		*this = a;
	}

	std::string union_address::to_string()
	{
		std::string ret;
		char ip[NI_MAXHOST] = "";
		if (_v4) {
			inet_ntop(AF_INET, &_addr.v4, ip, sizeof(ip));
			ret = ip;
		}
		else {
			inet_ntop(AF_INET6, &_addr.v6, ip, sizeof(ip));
			ret = ip;
			if (_scopeid) {
				ret += "%%" + std::to_string(_scopeid);
			}
		}
		return ret;
	}

	union_address & union_address::operator=(union_address const& a)
	{
		_v4 = a._v4;
		if (_v4) {
			_addr.v4.s_addr = a._addr.v4.s_addr;
		}
		else {
			memcpy(&_addr.v6, &a._addr.v6, sizeof(_addr.v6));
			_scopeid = a._scopeid;
		}
		return *this;
	}

	bool union_address::operator==(const union_address & a) const
	{
		if (_v4 != a._v4) return false;
		if (_v4) {
			return _addr.v4.s_addr == a._addr.v4.s_addr;
		}
		else {
			return memcmp(&_addr.v6, &a._addr.v6, sizeof(_addr.v6)) == 0 && _scopeid == a._scopeid;
		}
	}

	bool union_address::operator!=(const union_address & a) const
	{
		return !(*this == a);
	}

	bool union_address::operator<(const union_address & a) const
	{
		if (_v4 != a._v4)
			return _v4;
		if (_v4)
			return _addr.v4.s_addr < a._addr.v4.s_addr;
		int ret = memcmp(&_addr.v6, &a._addr.v6, sizeof(_addr.v6));
		if (ret < 0)
			return true;
		if (ret > 0)
			return false;
		return _scopeid < a._scopeid;
	}

	union_endpoint::union_endpoint(bool isTcp, bool isV4)
		:_addr(isV4)
		, _port(0)
		, _tcp(isTcp)
		, _soaddr(NULL)
	{
	}

	union_endpoint::union_endpoint(const char * ip, uint16_t p, bool isTcp, bool isV4)
		:_addr(isV4)
		, _port(p)
		, _tcp(isTcp)
		, _soaddr(NULL)
	{
		struct addrinfo hints, *res;
		memset(&hints, 0, sizeof(hints));
		hints.ai_family = (isV4 ? AF_INET : AF_INET6);
		int ret = getaddrinfo(ip, NULL, &hints, &res);
		if (ret != 0) {
			return;
		}
		switch (res->ai_family) {
		case AF_INET:
		{
			uint32_t v4 = 0;
			memcpy(&v4, &((sockaddr_in*)res->ai_addr)->sin_addr, sizeof(v4));
			_addr = union_address(v4);
			break;
		}
		case AF_INET6:
		{
			uint8_t v6[16];
			memset(v6, 0, sizeof(v6));
			memcpy(v6, &((sockaddr_in6*)res->ai_addr)->sin6_addr, sizeof(v6));
			_addr = union_address(v6, ((sockaddr_in6*)res->ai_addr)->sin6_scope_id);
			break;
		}
		default:
			;
		}
		freeaddrinfo(res);
	}

	union_endpoint::union_endpoint(const union_endpoint& ep)
		:_addr(ep._addr)
		, _port(ep._port)
		, _tcp(ep._tcp)
		, _soaddr(NULL)
	{
	}

	union_endpoint::~union_endpoint()
	{
		if (_soaddr)
			delete _soaddr;
	}

	bool union_endpoint::operator<(const union_endpoint &ep) const
	{
		if (_addr < ep._addr)
			return true;
		if (ep._addr < _addr)
			return false;
		return _port < ep._port;
	}

	int union_endpoint::from_socket_addr(const sockaddr * a, int len)
	{
		//assert(!_soaddr);
		// init inet version
		bool v4 = (a->sa_family == AF_INET);
		if (v4 != is_v4()) {
			union_address addr(v4);
			_addr = addr;
		}

		if (len < socket_addr_len())
			return -1;

		if (is_v4()) {
			sockaddr_in* s = (sockaddr_in*)a;
			_port = ntohs(s->sin_port);
			uint32_t v4 = 0;
			memcpy(&v4, &s->sin_addr, sizeof(v4));
			_addr = union_address(v4);
		}
		else {
			sockaddr_in6* s = (sockaddr_in6*)a;
			_port = ntohs(s->sin6_port);
			uint8_t v6[16];
			memset(v6, 0, sizeof(v6));
			memcpy(v6, &s->sin6_addr, sizeof(v6));
			_addr = union_address(v6, s->sin6_scope_id);
		}
		return 0;
	}

	const sockaddr * union_endpoint::socket_addr()
	{
		if (!_soaddr) { // allocate memory
			if (is_v4()) {
				sockaddr_in* a = new sockaddr_in;
				memset(a, 0, sizeof(sockaddr_in));
				a->sin_family = AF_INET;
				a->sin_port = htons(_port);
				memcpy(&a->sin_addr, &_addr.v4_addr(), sizeof(a->sin_addr));
				_soaddr = (sockaddr*)a;
			}
			else {
				sockaddr_in6* a = new sockaddr_in6;
				memset(a, 0, sizeof(sockaddr_in6));
				a->sin6_family = AF_INET6;
				a->sin6_port = htons(_port);
				a->sin6_scope_id = _addr.get_scope();
				memcpy(&a->sin6_addr, &_addr.v6_addr(), sizeof(a->sin6_addr));
				_soaddr = (sockaddr*)a;
			}
		}
		return _soaddr;
	}

	int union_endpoint::socket_addr_len()
	{
		return is_v4() ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
	}

	const char *union_endpoint::to_string()
	{
		std::string str = _tcp ? "tcp" : "udp";
		str += "|";
		str += _addr.to_string();
		str += "|";
		std::stringstream ss;
		ss << _port;
		str += ss.str();
		return str.c_str();
	}

	void SocketHelper::init()
	{
#ifdef _WIN32
		WSADATA wsaData;
		WORD wVersionRequested = MAKEWORD(2, 2);
		WSAStartup(wVersionRequested, &wsaData);
#endif
	}

	void SocketHelper::destroy()
	{
#ifdef _WIN32
		WSACleanup();
#endif
	}

	SOCKET SocketHelper::socket(const union_endpoint & ep)
	{
		return ::socket(ep.is_v4() ? AF_INET : AF_INET6,
			ep.is_tcp() ? SOCK_STREAM : SOCK_DGRAM, 0);
	}

	int SocketHelper::set_reuse(SOCKET sockfd, bool yes)
	{
		uint32_t flag = (yes ? 1 : 0);
		return ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&flag, sizeof(flag));
	}

	int SocketHelper::set_v6only(SOCKET sockfd, bool yes)
	{
		uint32_t flag = (yes ? 1 : 0);
		return ::setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&flag, sizeof(flag));
	}

	int SocketHelper::set_blocking(SOCKET sockfd, bool yes)
	{
#ifdef _WIN32
		u_long flag = (yes ? 0 : 1);
		return ::ioctlsocket(sockfd, FIONBIO, &flag);
#else
		::fcntl(sockfd, F_SETFL, O_NONBLOCK);
		int flags = ::fcntl(sockfd, F_GETFL, 0);
		if (flags < 0) return -1;
		flags = yes ? (flags&~O_NONBLOCK) : (flags | O_NONBLOCK);
		return ::fcntl(sockfd, F_SETFL, flags);
#endif
	}

	int SocketHelper::bind(SOCKET sockfd, union_endpoint & ep)
	{
		return ::bind(sockfd, ep.socket_addr(), ep.socket_addr_len());
	}

	int SocketHelper::listen(SOCKET listenfd, int backlog)
	{
		return ::listen(listenfd, backlog);
	}

	SOCKET SocketHelper::accept(SOCKET listenfd, union_endpoint & ep)
	{
		SOCKET s;
		sockaddr_in6 addr;
		socklen_t len = sizeof(addr);
		memset(&addr, 0, sizeof(addr));
		s = ::accept(listenfd, (sockaddr*)&addr, &len);
		if (s > 0) {
			if (ep.from_socket_addr((sockaddr*)&addr, len) != 0)
				return -1;
		}
		return s;
	}

	int SocketHelper::connect(SOCKET sockfd, union_endpoint & ep)
	{
		return ::connect(sockfd, ep.socket_addr(), ep.socket_addr_len());
	}

	int SocketHelper::send(SOCKET sockfd, const char * buf, size_t len)
	{
#ifdef _WIN32
		return ::send(sockfd, buf, static_cast<int>(len), 0);
#else
		return ::send(sockfd, buf, len, MSG_NOSIGNAL);
#endif
	}

	int SocketHelper::recv(SOCKET sockfd, char * buf, size_t len)
	{
#ifdef _WIN32
		return static_cast<int>(::recv(sockfd, buf, static_cast<int>(len), 0));
#else
		return ::recv(sockfd, buf, len, 0);
#endif
	}

	int SocketHelper::sendto(SOCKET sockfd, const char* buf, size_t len, union_endpoint & ep)
	{
#ifdef _WIN32
		return ::sendto(sockfd, buf, static_cast<int>(len), 0, ep.socket_addr(), ep.socket_addr_len());
#else
		return ::sendto(sockfd, buf, len, 0, ep.socket_addr(), ep.socket_addr_len());
#endif
	}

	int SocketHelper::recvfrom(SOCKET sockfd, char* buf, size_t len, union_endpoint & ep)
	{
		int ret;
		sockaddr_in6 addr;
		socklen_t alen = sizeof(addr);
		memset(&addr, 0, sizeof(addr));
#ifdef _WIN32
		ret = ::recvfrom(sockfd, buf, static_cast<int>(len), 0, (sockaddr*)&addr, &alen);
#else
		ret = ::recvfrom(sockfd, buf, len, 0, (sockaddr*)&addr, &alen);
#endif
		if (ret > 0) {
			if (ep.from_socket_addr((sockaddr*)&addr, alen) != 0)
				return -1;
		}
		return ret;
	}

	void SocketHelper::close(SOCKET fd)
	{
#ifdef _WIN32
		::closesocket(fd);
#else
		::close(fd);
#endif
	}

	int SocketHelper::last_error()
	{
#ifdef _WIN32
		return GetLastError();
#else
		return errno;
#endif
	}
	bool SocketHelper::again()
	{
#ifdef _WIN32
		int e = ::WSAGetLastError();
		//LOGD << "Error is : " << e;
		if (e == WSAEINPROGRESS || e == WSAEALREADY || e == WSAEWOULDBLOCK || e == WSAEINVAL) {
			return true;
		}
		else {
			return false;
		}
#else
		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS || errno == EALREADY) {
			return true;
		}
		else {
			return false;
		}
#endif
	}

	void SocketHelper::net_htons(uint16_t h, uint8_t *& n)
	{
		h = htons(h);
		memcpy(n, &h, sizeof(uint16_t));
	}

	uint16_t SocketHelper::net_ntohs(const uint8_t * n)
	{
		uint16_t s;
		memcpy(&s, n, sizeof(uint16_t));
		return ntohs(s);
	}

	bool SocketHelper::is_valid_ipv4_address(const char * ip)
	{
		if (!ip) {
			return false;
		}

		struct sockaddr_in sa;
		int result = inet_pton(AF_INET, ip, &(sa.sin_addr));
		return result != 0;
	}

	bool SocketHelper::is_valid_address(const char * addr)
	{
		struct addrinfo hints, *res;
		memset(&hints, 0, sizeof(hints));
		hints.ai_family = AF_INET;
		int ret = getaddrinfo(addr, NULL, &hints, &res);
		if (ret != 0) {
			return false;
		}
		else {
			freeaddrinfo(res);
			return true;
		}
	}
}